Android 11 的存储升级和迁移

Android 11 正式发布至今已经有一段时间了,也有越来越多的设备升级或搭载了最新系统。Google 也要求 App 开发者将 targetSdkVersion 升级到 30,并给出了 deadline(今年下半年)。在 TargetSdkVersion 迁移过程中感知最大的改动是存储,api 30 要求必须适配 Scope Storage

Scope Storage 是什么

Scope Storage 最初在 Android 10 引入,但当时并不做强制要求,其目的是限制 App 对外部存储的随意访问。在此之前,App 为了能访问用户媒体数据会申请读写权限,用户授予权限之后 App 能做的其实不止于访问媒体数据,还可以获取外部存储的读写权限,这会导致很多问题,比如 App 可以随意的在外部存储中创建文件/目录,并且 App 在被卸载之后,这些创建在外部存储的数据依然会被保留,还可以随意读取其他 App 创建的数据,会导致很多数据安全问题等等,Android 普通用户对这些问题的抱怨由来已久,而 Scope Storge 就是 Google 给出的解决方案。

其实际是一套存储要求和访问限制的规范,在其规范里:

  • App 读写自己的文件不需要权限
  • App 读取其他 App 的媒体文件需要 READ_EXTERNAL_STORAGE 权限
  • App 只有在用户直接同意的情况下才能写入其他App的媒体文件(文件管理器/系统相册等除外,它们没有这个限制)
  • 不能访问其他 App 的外部存储数据目录

可以发现,正如其字面意思,Scope Storage 的思路就是先把 App 的读写权限限制在自己的范围,然后再看 App 通常有哪些合理的外部访问需求,并将这些需求相关的接口尽可能按最低访问限度开放。

如何访问文件

对应前文中给出的访问限制,Android 也给出了具体类型的 Api 使用方式:

文件类型 访问方式 权限要求 其他 App 访问情况 App卸载后, 文件是否删除
App 自己的文件 getFilesDir()
/getExternalFilesDir()
不需要权限 不能访问
媒体类共享文件 MediaStore API Android 11/10:
访问其他App创建的文件需要 READ_EXTERNAL_STORAGE
Android 9及以下访问任意文件都需要权限
申请权限后可以访问
文档/其他共享文件 SAF
(Storage Access Framework)
不需要权限 可以通过 SAF 访问

表中前两行在前文访问限制规则中已经提及,在此不做补充说明。末行的“非媒体类共享文件”通过 SAF 访问也不需要权限,这个最常见的场景就是让用户通过文件选择器从设备下载目录选择一个文件读写的情况。

SAF 是 Android 4.4 就引入的文件访问框架,可以方便和安全的把用户文件选择/读取的工作交与系统完成。关于 SAF 的 API 使用本文不过多讨论,详情可参看 官方文档

如何做迁移适配

对于这么大的系统 API 改动,了解其在新系统上的使用方式只是一方面,更重要的是如何做好已有 App 的迁移升级工作。

无需特别适配

如果你的 App 对存储的读写仅限于自身的文件或者是通过系统文件选择器(i.e. SAF)访问文件,那几乎不需要做改动。

注意,这里“自身文件”仅限于通过`getFilesDir`/`getExternalFilesDir` 访问自身私有目录的情况。

此外,如果还无需访问图库等媒体资源(其他App创建的)的话,那权限请求在 Android 11 也是不需要的,为保持仅申请必须权限的原则,可以做以下改动:

1
2
3
4
<uses-permission  
android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="29" />

其他

其他情况,诸如使用 getExternalStorageDirectory (此API已废弃) 去访问外部存储,就需要做适配了:

先发布一个 targetApi 到 29及以下的版本,在这个版本中需要:

  1. 如果是 target 到 29 的话,需要在 manifest 文件中添加 android:requestLegacyExternalStorage="true",这会让系统继续允许App 使用之前的方式访问外部存储。
  2. 根据 App 的业务需求通过 File API 把存放在外部存储(/sdcard/)的私有文件迁移到 getExternalFilesDir() 返回的目录下
  3. 共享的非媒体文件迁移到 Downloads/ 路径下
  4. 迁移完成后删除原文件夹

迁移完成后,再发布一个 TargetApi 到 30 的版本即可。

这个流程中需要注意的:

  • requestLegacyExternalStorage 在 targetApi 30 后无效,系统会直接忽视。

  • 如果部分老旧版本 App 没有升级到这个 targetApi 29 的过渡版本,而是直接升级到 targetApi 30 的版本,且运行在 Android 11 的设备上,那该如何处理呢?

    针对这种情况,API 30 提供了 preserveLegacyStorage 这个flag,设置为 true 之后,如果 App 是升级到 API 30 版本的话,那么requestLegacyExternalStorage 的设置依然有效,可以继续通过上诉流程完成迁移。

    preserveLegacyStorage 仅对升级的 App 有效,对于首次安装,或者卸载重新安装无效。

References